import { FromClause, IFromDescriptorBuilder, ValuePredicateFn } from '~/core/dsl';
import { getRandomString, QEngineCapability, QIssueTemplate, QNative } from '~/adapter/Defines';
import { CodeNode, Value } from 'qvog-types';
import { Location } from 'qvog-dsl';
import { Configuration } from '~/core/Configuration';
import { FlowClause, IFlowDescriptorBuilder } from '~/core/dsl/fluent/FlowDescriptor';
import { TaintFlow } from '~/lib/flow';

function getValueLocation(value: Value): Location {
    const property = (Configuration.getContext().getNode(value) as CodeNode).property;
    return {
        file: property.file,
        range: {
            start: {
                line: property.lineno,
                column: 0,
            },
            end: {
                line: property.lineno,
                column: 0,
            },
        },
    };
}

export function getEngineCapability(): QEngineCapability {
    const capability: QEngineCapability = {
        name: 'qvog',
        config: {},
        api: {
            getLocation: getValueLocation,
            report: undefined!,
        },
        native: undefined!,
    };

    capability.native = getNativeCapability();
    capability.api.report = function (template: QIssueTemplate): void {
        capability.native.context.descriptor.select('__match__', template.message);
    };

    return capability;
}

function getNativeCapability(): QNative {
    const native: QNative = {
        context: {
            // descriptor will be set when the query begins
            descriptor: undefined!,
        },
        api: {
            nodes: undefined!,
            predicate: undefined!,
            exists: undefined!,
        },
    };

    native.api.nodes = function (predicate: ValuePredicateFn, alias?: string): string {
        alias = alias || getRandomString(8);
        const clause: FromClause = (clause: IFromDescriptorBuilder) => {
            return clause.withData(predicate).as(alias);
        };
        native.context.descriptor.from(clause);
        return alias;
    };

    native.api.predicate = function (predicate: ValuePredicateFn, alias?: string): string {
        alias = alias || getRandomString(8);
        const clause: FromClause = (clause: IFromDescriptorBuilder) => {
            return clause.withPredicate(predicate).as(alias);
        };
        native.context.descriptor.from(clause);
        return alias;
    };

    native.api.exists = function (sourceNode: Value, barrier: string, sink: string, alias?: string, flow?: () => IFlowDescriptorBuilder): boolean {
        alias = alias || getRandomString(8);
        flow = flow || (() => new TaintFlow());
        const source = "Source";
        native.context.descriptor.from((clause) => clause.withData((n) => n.id === sourceNode.id).as(source));
        const clause: FlowClause = (clause: IFlowDescriptorBuilder) => {
            return clause.source(source).barrier(barrier).sink(sink).as(alias);
        };
        native.context.descriptor.exists(clause, flow);
        // Return true doesn't mean the flow exists, the results are stored in the table named ${alias}, return true is just for report the table.
        return true;
    }

    return native;
}
